#pragma once
#include "../host_utils.hpp"
#include <thrust/extrema.h>
#include "dev_utils.hpp"
#include "../host_utils.hpp"
#include "../UnifiedMemManaged.hpp"

class Vector3fDev : public UnifiedMemManaged
{
public:
    float x, y, z;
    __device__ Vector3fDev() : x(0), y(0), z(0) {}
    __device__ Vector3fDev(float tx, float ty, float tz) : x(tx), y(ty), z(tz) {}
    Vector3fDev(float tx, float ty, float tz, bool isFromHost) : x(tx), y(ty), z(tz) { assert(isFromHost); }

    __device__ Vector3fDev operator/(float n) const
    {
        return Vector3fDev(x / n, y / n, z / n);
    }

    __device__ Vector3fDev operator-(const Vector3fDev &other) const
    {
        return Vector3fDev(x - other.x, y - other.y, z - other.z);
    }
    Vector3fDev host_sub(const Vector3fDev &other) const
    {
        return Vector3fDev(x - other.x, y - other.y, z - other.z, true);
    }

    __device__ Vector3fDev operator-() const { return Vector3fDev(-x, -y, -z); }

    __device__ Vector3fDev operator*(const float &r) const
    {
        return Vector3fDev(x * r, y * r, z * r);
    }

    __device__ Vector3fDev operator*(const Vector3fDev &v) const
    {
        return Vector3fDev(x * v.x, y * v.y, z * v.z);
    }

    __device__ Vector3fDev operator+(const Vector3fDev &v) const
    {
        return Vector3fDev(x + v.x, y + v.y, z + v.z);
    }

    __device__ static Vector3fDev Min(const Vector3fDev &p1, const Vector3fDev &p2)
    {
        return Vector3fDev(dev_min(p1.x, p2.x), dev_min(p1.y, p2.y),
                           dev_min(p1.z, p2.z));
    }

    __device__ static Vector3fDev Max(const Vector3fDev &p1, const Vector3fDev &p2)
    {
        return Vector3fDev(dev_max(p1.x, p2.x), dev_max(p1.y, p2.y),
                           dev_max(p1.z, p2.z));
    }

    static Vector3fDev Min(const Vector3fDev &p1, const Vector3fDev &p2, bool isFromHost)
    {
        assert(isFromHost);
        return Vector3fDev(host_min(p1.x, p2.x), host_min(p1.y, p2.y),
                           host_min(p1.z, p2.z), true);
    }

    static Vector3fDev Max(const Vector3fDev &p1, const Vector3fDev &p2, bool isFromHost)
    {
        assert(isFromHost);
        return Vector3fDev(host_min(p1.x, p2.x), host_min(p1.y, p2.y),
                           host_min(p1.z, p2.z), true);
    }

    __device__ float norm() { return sqrt(x * x + y * y + z * z); }
    float norm(bool isFromHost) { return sqrt(x * x + y * y + z * z); }
};

__device__ static Vector3fDev normalize(const Vector3fDev &v)
{
    float mag2 = v.x * v.x + v.y * v.y + v.z * v.z;
    if (mag2 > 0)
    {
        float invMag = 1 / sqrt(mag2);
        return Vector3fDev(v.x * invMag, v.y * invMag, v.z * invMag);
    }

    return v;
}

static Vector3fDev normalize(const Vector3fDev &v, bool isFromHost)
{
    assert(isFromHost);
    float mag2 = v.x * v.x + v.y * v.y + v.z * v.z;
    if (mag2 > 0)
    {
        float invMag = 1 / sqrt(mag2);
        return Vector3fDev(v.x * invMag, v.y * invMag, v.z * invMag, true);
    }

    return v;
}

__device__ static float dotProduct(const Vector3fDev &a, const Vector3fDev &b)
{
    return a.x * b.x + a.y * b.y + a.z * b.z;
}

__device__ static Vector3fDev crossProduct(const Vector3fDev &a, const Vector3fDev &b)
{
    return Vector3fDev(
        a.y * b.z - a.z * b.y,
        a.z * b.x - a.x * b.z,
        a.x * b.y - a.y * b.x);
}

static Vector3fDev crossProduct(const Vector3fDev &a, const Vector3fDev &b, bool isFromHost)
{
    assert(isFromHost);
    return Vector3fDev(
        a.y * b.z - a.z * b.y,
        a.z * b.x - a.x * b.z,
        a.x * b.y - a.y * b.x, true);
}

class Vector2fDev : public UnifiedMemManaged
{
public:
    Vector2fDev() : x(0), y(0) {}
    Vector2fDev(float xx) : x(xx), y(xx) {}
    Vector2fDev(float xx, float yy) : x(xx), y(yy) {}
    Vector2fDev operator*(const float &r) const { return Vector2fDev(x * r, y * r); }
    Vector2fDev operator+(const Vector2fDev &v) const { return Vector2fDev(x + v.x, y + v.y); }
    float x, y;
};
